package lion.json;

import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang.StringUtils;

import lion.dev.lang.MapJ;

public class JsonParser {

	private StringReader reader;
	private int ch = -1;
	private char[] unicode = new char[4];
	private MapJ result;

	public JsonParser(String str) {

		if (!str.trim().endsWith("}")) { throw new JsonSyntaxException("Json Object must end with }"); }
		this.reader = new StringReader(str);
	}

	public MapJ parse() {

		if (result == null) {
			try {
				result = parseObject();
			} catch (JsonSyntaxException e) {
				throw e;
			} catch (IOException e) {
				throw new JsonSyntaxException("parse json error", e);
			}
		}
		return result;
	}

	/**
	 * parse json object
	 */
	private MapJ parseObject() throws JsonSyntaxException, IOException {

		skipBlank();
		ch = reader.read();
		if (ch != '{') { throw new JsonSyntaxException("Json Object must start with {"); }
		MapJ obj = new MapJ();
		// read key-values
		while ((ch = reader.read()) != '}') {
			reader.skip(-1);
			skipBlank();
			String name = parseString();
			skipBlank();
			ch = reader.read();
			if (ch != ':') { throw new JsonSyntaxException("need ':' after a key near " + name); }
			skipBlank();
			Object value = null;
			ch = reader.read();
			reader.skip(-1);
			if (ch == '"') {
				value = parseString();
			} else if (ch == '[') {
				value = parseArray();
			} else if (ch == '{') {
				value = parseObject();
			} else {
				value = parseNormal();
			}
			obj.put(name, value);
			skipBlank();
			ch = reader.read();
			if (ch != ',') {
				break;
			}
		}
		return obj;
	}

	/**
	 * parse json string <br/>
	 */
	private String parseString() throws IOException {

		StringBuilder sbu = new StringBuilder();

		boolean isString = false;
		while ((ch = reader.read()) != -1) {
			if (ch == '\n' || ch == '\r') { throw new JsonSyntaxException("broken string"); }
			if (ch == '"') {
				if (!isString) {
					isString = true;
					continue;
				} else {
					isString = false;
				}
			} else if (ch == '\\') {
				ch = (char) reader.read();
				switch (ch) {
				case 'n':
					ch = '\n';
					break;
				case 'r':
					ch = '\r';
					break;
				case 'b':
					ch = '\b';
					break;
				case 'f':
					ch = '\f';
					break;
				case 't':
					ch = '\t';
					break;
				case '\\':
					ch = '\\';
					break;
				case '"':
					ch = '\"';
					break;
				case 'u':
					reader.read(unicode);
					ch = Integer.parseInt(new String(unicode), 16);
					break;
				default:
					sbu.append('\\');
				}
			}
			if (!isString) {
				break;
			}
			sbu.append((char) ch);
		}
		if (ch != '"') { throw new JsonSyntaxException("string must end with \" " + sbu.toString()); }
		return sbu.toString();
	}

	/**
	 * parse normal value like boolean,number,null
	 */
	private Object parseNormal() throws IOException {

		StringBuilder sbu = new StringBuilder();
		while ((ch = reader.read()) != ' ') {

			if (ch == ',' || ch == '}') {
				reader.skip(-1);
				break;
			}
			sbu.append((char) ch);
		}
		String str = sbu.toString().toString().trim();
		if ("null".equalsIgnoreCase(str) || StringUtils.isBlank(str)) { return null; }
		if ("true".equalsIgnoreCase(str) || "false".equalsIgnoreCase(str)) { return Boolean.valueOf(str); }

		if (str.indexOf(".") > 0) { return Double.valueOf(str); }

		try {
			return Integer.valueOf(str);
		} catch (Exception e) {
			return Long.valueOf(str);
		}
	}

	/**
	 * parse json array
	 */
	private Object[] parseArray() throws IOException {

		List<Object> result = new ArrayList<>();

		ch = reader.read();
		if (ch != '[') { throw new JsonSyntaxException("array must start with ["); }
		while ((ch = reader.read()) != ']') {
			reader.skip(-1);
			skipBlank();
			Object val = null;
			if (ch == '"') {
				val = parseString();
			} else if (ch == '[') {
				val = parseArray();
			} else if (ch == '{') {
				val = parseObject();
			} else {
				val = parseNormal();
			}
			result.add(val);
			skipBlank();
			ch = reader.read();
			if (ch != ',') {
				break;
			}
			skipBlank();
		}
		if (ch != ']') { throw new JsonSyntaxException("json array must end with ]"); }
		return result.toArray();
	}

	/**
	 * skip useless blanks like \n,\r space
	 */
	private void skipBlank() throws IOException {

		while (true) {
			ch = reader.read();
			switch (ch) {
			case '\n':
			case '\r':
			case ' ':
			case '\t':
				continue;
			}
			break;
		}
		reader.skip(-1);
	}
}
